package lol.clann.minecraft.springboot.plugin.module.devtools.commands;

import com.ruoshui.utils.java.exception.ExceptionUtils;
import com.ruoshui.utils.java.reflect.ReflectUtils;
import com.ruoshui.utils.java.unsafe.UnsafeUtils;
import lol.clann.minecraft.springboot.api.annotation.command.Command;
import lol.clann.minecraft.springboot.api.annotation.command.CommandDefine;
import lol.clann.minecraft.springboot.api.annotation.command.Sender;
import lol.clann.minecraft.springboot.api.bukkit.command.tabcomplete.AbstractListTabComplete;
import lol.clann.minecraft.springboot.api.bukkit.command.tabcomplete.BooleanTabComplete;
import lol.clann.minecraft.springboot.api.bukkit.model.EventType;
import lol.clann.minecraft.springboot.api.bukkit.utils.EventUtils;
import lol.clann.minecraft.springboot.api.context.SpringContext;
import lol.clann.minecraft.springboot.api.service.FlagService;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.plugin.RegisteredListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import relocate.com.fasterxml.jackson.annotation.JsonAutoDetect;
import relocate.com.fasterxml.jackson.annotation.PropertyAccessor;
import relocate.com.fasterxml.jackson.core.JsonProcessingException;
import relocate.com.fasterxml.jackson.databind.ObjectMapper;
import relocate.com.fasterxml.jackson.databind.ObjectWriter;
import relocate.com.fasterxml.jackson.databind.node.ObjectNode;

import javax.annotation.PostConstruct;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * @author pyz
 * @date 2019/5/19 2:03 PM
 */
@Slf4j
@CommandDefine(name = "devtools", order = -1)
public class EventDebugger implements Listener {

    private Map<Class, Boolean> eventStatuMap = new HashMap<>();
    private Map<String, Class> eventMap = new HashMap<>();
    private List<String> tabCompleteCandidates = new ArrayList<>();
    private Map<Class, List<Method>> methodCache = new HashMap<>();
    private ObjectMapper objectMapper;
    private ObjectWriter objectWriter;
    @Autowired
    private EventUtils eventUtils;
    @Autowired
    private FlagService flagService;

    @PostConstruct
    private void init() {
        for (EventType eventType : eventUtils.getEventTypes()) {
            registerEvent(eventType.getType());
        }
//      调整jackson只从getter获取
        objectMapper = SpringContext.getBean(ObjectMapper.class);
        objectMapper = objectMapper.copy();
        objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.PUBLIC_ONLY);
        objectWriter = objectMapper.writerWithDefaultPrettyPrinter();
    }

    private void registerEvent(Class eventClass) {
        eventMap.put(eventClass.getSimpleName(), eventClass);
        eventStatuMap.put(eventClass, false);
        tabCompleteCandidates.add(eventClass.getSimpleName());
        if (Cancellable.class.isAssignableFrom(eventClass)) {
            flagService.register("event.cancel." + eventClass.getSimpleName());
            flagService.set("event.cancel." + eventClass.getSimpleName(), false);
        }
    }

    @Command(showArgs = "event <eventSimpleClassName> <true/false>", des = "开启/关闭指定事件的调试信息", tabComplete = EventTabComplete.class)
    private void event(@Sender CommandSender sender, String eventSimpleClassName, boolean status) {
        Class eventClass = eventMap.get(eventSimpleClassName);
        if (eventClass == null) {
            sender.sendMessage("无效的事件名:" + eventSimpleClassName);
        }
        List<Class> eventClasses = new ArrayList<>(eventStatuMap.keySet());
        for (Class item : eventClasses) {
            if (eventClass.isAssignableFrom(item)) {
                eventStatuMap.put(item, status);
                if (status) {
                    if (registerDebugListener(item)) {
                        sender.sendMessage("register debug listener for event:" + item.getSimpleName());
                    }
                } else {
                    if (unregisterDebugListener(item)) {
                        sender.sendMessage("unregister debug listener for event:" + item.getSimpleName());
                    }
                }
            }
        }
    }

    private Map<Class, RegisteredListener> listenerMap = new HashMap<>();

    private boolean registerDebugListener(Class eventClass) {
        synchronized (listenerMap) {
            if (listenerMap.containsKey(eventClass)) {
                return false;
            }
            RegisteredListener registeredListener = eventUtils.registerListener(eventClass, this, EventPriority.MONITOR, false, (listener, event) -> {
                onEvent(event);
            });
            listenerMap.put(eventClass, registeredListener);
            return true;
        }
    }

    private boolean unregisterDebugListener(Class eventClass) {
        synchronized (listenerMap) {
            RegisteredListener registeredListener = listenerMap.remove(eventClass);
            if (registeredListener == null) {
                return false;
            }
            eventUtils.unregister(eventClass, registeredListener);
            return true;
        }
    }

    private void onEvent(Event event) {
        ObjectNode json = objectMapper.createObjectNode();
        List<Method> cache = methodCache.get(event.getClass());
        if (cache == null) {
            cache = new ArrayList<>();
            List<Method> methods = ReflectUtils.getAllMethods(event.getClass());
            for (Method method : methods) {
                if (!Modifier.isStatic(method.getModifiers()) &&
                        Modifier.isPublic(method.getModifiers()) &&
                        (method.getName().startsWith("get") || method.getName().startsWith("is")) &&
                        method.getParameterCount() == 0) {
                    cache.add(method);
                }
            }
            methodCache.put(event.getClass(), cache);
        }
        for (Method method : cache) {
            String name = method.getName().startsWith("get") ? method.getName().substring(3) : method.getName().substring(2);
            name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
            try {
                json.put(name, String.valueOf(method.invoke(event)));
            } catch (IllegalAccessException | InvocationTargetException e) {
                ExceptionUtils.throwException(e);
            }
        }
        if (event instanceof Cancellable) { // 判断是否取消事件
            if (Objects.equals("true", flagService.get("event.cancel." + event.getClass().getSimpleName()))) {
                ((Cancellable) event).setCancelled(true);
            }
        }
        json.put("class", event.getClass().getName());
        json.put("hashCode", event.hashCode());
        json.put("memAddress", UnsafeUtils.getAddress(event));
        json.put("timestamp", System.currentTimeMillis());
        json.put("nanoTime", System.nanoTime());
        String str = null;
        try {
            str = objectWriter.writeValueAsString(json);
        } catch (JsonProcessingException e) {
            ExceptionUtils.throwException(e);
        }
        Bukkit.getConsoleSender().sendMessage(str);
//      打印一行时间戳,不然容易搞晕
        Bukkit.getConsoleSender().sendMessage("nanoTime: " + String.valueOf(System.nanoTime()));
    }


    @Component
    public static class EventTabComplete extends AbstractListTabComplete {
        @Autowired
        private EventDebugger eventDebugger;

        @Override
        protected List<String> getCandidates(CommandSender sender, String alias, String[] args) {
            if (args.length > 3) {
                return new ArrayList<>();
            }
            if (args.length == 2) {
                return eventDebugger.tabCompleteCandidates;
            } else {
                return BooleanTabComplete.booleanCandidates;
            }
        }
    }
}
